use std::collections::HashMap;
use std::fs;
use std::io;
use std::path::PathBuf;
use std::sync::Arc;

use anyhow::Result;

use hyper::service::make_service_fn;
use hyper::service::service_fn;
use hyper::Body;
use hyper::Method;
use hyper::Request;
use hyper::Response;
use hyper::Server;
use hyper::StatusCode;
use new_string_template::template::Template;

use syntect::highlighting::ThemeSet;
use syntect::html::highlighted_html_for_file;
use syntect::parsing::SyntaxSet;
use tokio::sync::RwLock;

const FAVICON: &str = "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";

const DIR_TEMPLATE: &str = r#"<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="icon" type="image/x-icon" href="data:image/ico;base64,{favicon}">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.css" type="text/css" rel="stylesheet">
    <title>Rust Web Server</title>
</head>
<body>
<div class="container px-4">
    <h1>Rust Web Server</h1>
    <div class="row gx-5">
        <nav area-label="breadcrumb">
            <ol class="breadcrumb">
                {breadcrumb}
            </ol>
        </nav>
    </div>
    <div class="row gx-5">
        <ul class="list-group">
        {content}
        </ul>
    </div>
</div>
</body>
</html>"#;

const FILE_TEMPLATE: &str = r#"<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Rust Web Server</title>
    <link rel="icon" type="image/x-icon" href="data:image/ico;base64,{favicon}">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" type="text/css">
</head>
<body>
<div class="container">
    <h1>Rust Web Server</h1>
    <div class="row gx-5">
        <nav area-label="breadcrumb" style="--bs-breadcrumb-divider: '>';">
            <ol class="breadcrumb">
                {breadcrumb}
            </ol>
        </nav>
    </div>
    <div class="row gx-5">
    {content}
    </div>
</div>
</body>
</html>"#;

const ERROR_TEMPLATE: &str = r#"<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="icon" type="image/x-icon" href="data:image/ico;base64,{favicon}">
    <title>Rust Web Server</title>
</head>
<body>
    <h1>Rust Web Server</h1>
    {content}
</body>
</html>"#;

fn generate_breadcrumb(path: &str) -> String {
    let mut breadcrumb = String::new();
    if !path.is_empty() {
        breadcrumb.push_str("<li class=\"breadcrumb-item\"><a href=\"/\">首页</a></li>");
        let mut split_path = PathBuf::from("");
        let mut count = path.split("/").count();
        for it in path.split("/").into_iter() {
            count -= 1;
            split_path.push(it);
            if count == 0 {
                breadcrumb.push_str(
                    format!(
                        "<li class=\"breadcrumb-item active\" area-current=\"page\">{}</li>",
                        it
                    )
                    .as_str(),
                );
            } else {
                breadcrumb.push_str(
                    format!(
                        "<li class=\"breadcrumb-item\"><a href=\"/{}\">{}</a></li>",
                        split_path.display(),
                        it
                    )
                    .as_str(),
                );
            }
        }
    } else {
        breadcrumb.push_str("<li class=\"breadcrumb-item active\" area-current=\"page\">首页</li>");
    }
    breadcrumb
}

pub(crate) async fn highlight_file(file_path: &str) -> Result<String, io::Error> {
    let syntax_set = SyntaxSet::load_defaults_newlines();
    let theme_set = ThemeSet::load_defaults();
    let theme = &theme_set.themes["base16-ocean.light"];
    let highlighted_html = highlighted_html_for_file(file_path, &syntax_set, theme).unwrap();
    Ok(String::from_utf8(highlighted_html.into_bytes()).unwrap())
}

async fn handler_file(file_path: PathBuf, root: PathBuf) -> Result<String> {
    let path = file_path.to_str().unwrap();
    log::debug!("加载文件：{path}");
    let content = highlight_file(file_path.as_path().to_str().unwrap()).await?;
    let mut params = HashMap::new();
    params.insert("content", content);
    params.insert(
        "breadcrumb",
        generate_breadcrumb(
            file_path
                .strip_prefix(root.to_str().unwrap())
                .unwrap()
                .to_str()
                .unwrap(),
        ),
    );
    params.insert("favicon", FAVICON.to_string());
    params.insert("ext", "txt".to_string());
    let temp_str = FILE_TEMPLATE;

    match Template::new(temp_str).render(&params) {
        Ok(content) => Ok(content),
        Err(e) => Err(e.into()),
    }
}

async fn handler_dir(dir_path: PathBuf, root: PathBuf) -> Result<String> {
    let path = dir_path.to_str().unwrap();
    log::debug!("加载目录：{path}");
    let content = match dir_path.read_dir() {
        Ok(dir) => dir
            .map(|d| {
                let file_name = d.unwrap().file_name().into_string().unwrap();
                let cur_relative_path: PathBuf = vec![
                    dir_path
                        .strip_prefix(root.to_str().unwrap())
                        .unwrap()
                        .to_str()
                        .unwrap(),
                    file_name.as_str(),
                ]
                .iter()
                .collect();
                log::debug!("扫描目录: {:?}: {:?}", dir_path, cur_relative_path);
                format!(
                    "<li class=\"list-group-item\"><a href=\"/{}\" class=\"link-dark\">{}</a></li>",
                    cur_relative_path.display(),
                    file_name
                )
            })
            .collect::<Vec<String>>()
            .join(""),
        Err(_) => String::new(),
    };
    let mut up_page_link = if dir_path.to_str().unwrap_or("").len() > 0 {
        match dir_path
            .parent()
            .unwrap()
            .strip_prefix(root.to_str().unwrap())
        {
            Ok(parent_path) => format!(
                "<li class=\"list-group-item\"><a href=\"/{}\">..</a></li>",
                parent_path.display()
            ),
            Err(e) => {
                log::error!("生成上一页错误：{e}");
                "".to_string()
            }
        }
    } else {
        "".to_string()
    };
    log::debug!("上一页: {}", up_page_link);
    up_page_link.push_str(content.as_str());
    let mut params = HashMap::new();
    params.insert("content", up_page_link);
    params.insert("favicon", FAVICON.to_string());
    params.insert(
        "breadcrumb",
        generate_breadcrumb(
            dir_path
                .strip_prefix(root.to_str().unwrap())
                .unwrap()
                .to_str()
                .unwrap(),
        ),
    );
    let tmpl_str = DIR_TEMPLATE;

    match Template::new(tmpl_str).render(&params) {
        Ok(content) => Ok(content),
        Err(e) => Err(e.into()),
    }
}

fn handler_error(msg: String, status_code: StatusCode, e: Option<anyhow::Error>) -> Response<Body> {
    log::error!("msg: {msg}, statuc_code: {status_code}, error: {e:?}");
    let mut params = HashMap::new();
    let content = match e {
        Some(e) => format!("{msg}: {e}"),
        None => msg,
    };
    params.insert("content", content);
    let tmpl_str = ERROR_TEMPLATE;
    match Template::new(tmpl_str).render(&params) {
        Ok(content) => Response::builder()
            .status(status_code)
            .body(Body::from(content))
            .unwrap(),
        Err(e) => Response::builder()
            .status(StatusCode::INTERNAL_SERVER_ERROR)
            .body(Body::from(format!("模板错误：{e}")))
            .unwrap(),
    }
}

async fn handler_request(
    req: &Request<Body>,
    current_dir: &Arc<RwLock<PathBuf>>,
) -> Result<Response<Body>, hyper::Error> {
    let current_dir = current_dir.clone();

    let response = match req.method() {
        &Method::GET => {
            let path = req.uri().path();
            log::info!("加载: {path}");
            let mut file_path = PathBuf::new();
            let root = current_dir.read().await.clone();
            file_path.push(current_dir.read().await.clone());
            file_path.push(&path[1..]);
            let metadata = fs::metadata(file_path.as_path()).unwrap();
            if metadata.is_dir() {
                match handler_dir(file_path, root).await {
                    Ok(content) => Response::new(Body::from(content)),
                    Err(e) => {
                        log::error!("{path}, {e}");
                        Response::builder()
                            .status(StatusCode::NOT_FOUND)
                            .body(Body::from(e.to_string()))
                            .unwrap()
                    }
                }
            } else if metadata.is_file() {
                match handler_file(file_path, root).await {
                    Ok(content) => Response::new(Body::from(content)),
                    Err(e) => {
                        log::error!("{path}, {e}");
                        Response::builder()
                            .status(StatusCode::NOT_FOUND)
                            .body(Body::from(e.to_string()))
                            .unwrap()
                    }
                }
            } else {
                handler_error(
                    format!("文件类型错误：{:?}", metadata.file_type()),
                    StatusCode::NOT_ACCEPTABLE,
                    None,
                )
            }
        }
        _ => handler_error(
            format!("不允许此操作: {}", req.method()),
            StatusCode::METHOD_NOT_ALLOWED,
            None,
        ),
    };

    Ok::<_, hyper::Error>(response)
}

pub async fn start_server(bind: &str, dir: PathBuf, browser: bool) -> Result<()> {
    let current_dir = Arc::new(RwLock::new(dir));
    let make_service = make_service_fn(move |_| {
        let current_dir = current_dir.clone();
        async move {
            Ok::<_, hyper::Error>(service_fn(move |req| {
                let current_dir = current_dir.clone();
                async move { handler_request(&req, &current_dir).await }
            }))
        }
    });
    let addr = bind.parse().unwrap();
    let server = Server::bind(&addr).serve(make_service);
    let url = format!("http://{bind}");
    log::info!("通过 {} 访问网站", url);
    if browser {
        match webbrowser::open(url.as_str()) {
            Ok(_) => log::info!("已在浏览器打开"),
            Err(e) => log::error!("打开失败：{e}"),
        }
    }
    if let Err(e) = server.await {
        log::error!("{e}");
    }

    Ok(())
}
